﻿using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;
using Boxi.Model;
using Boxi.EventArguments;
using Boxi.Extensions;

namespace Boxi.ViewModel
{
    public class GameView : INotifyPropertyChanged
    {
        #region Fields
        public GameModel GameData;
        private Canvas Parent;
#if (DEBUG)
        public static TimeSpan MoveSpeed = new TimeSpan(0, 0, 0, 0, 150);
#else
        public static TimeSpan MoveSpeed = new TimeSpan(0, 0, 0, 0, 600);
#endif
        #endregion
        #region EventHandlers
        public event EventHandler<EventArgs> OnPlayerChanged;
        public event EventHandler<PlayerEventArgs> OnComputerMoveStarted;
        public event EventHandler<PlayerEventArgs> OnComputerMoveCompleted;
        public event EventHandler<GameOverEventArgs> OnGameOver;
        public event EventHandler<MoveMadeEventArgs> OnAfterMoveMade;
        public event EventHandler<EventArgs> OnAfterActivationComplete;
        #endregion
        #region Events
        public event PropertyChangedEventHandler PropertyChanged;
        #endregion
        #region Properties

        /// <summary>
        /// 
        /// </summary>
        public Boolean IsPaused 
        { 
            get;
            set;
        }
        /// <summary>
        /// 
        /// </summary>
        public Boolean IsAutoMove
        {
            get;
            set;
        }
        /// <summary>
        /// 
        /// </summary>
        public GameModel.ThemeType Theme
        {
            get;
            set;
        }
        /// <summary>
        /// 
        /// </summary>
        public DispatcherTimer AutoFillTimer
        {
            get;
            set;
        }
        /// <summary>
        /// 
        /// </summary>
        public DispatcherTimer ComputerTurnTimer
        {
            get;
            set;
        }
        public GameModel.DifficultyType Difficulty
        {
            get;
            set;
        }
        public PlayerView[] Players
        {
            get;
            set;
        }
        /// <summary>
        /// 
        /// </summary>
        public MoveView[] Moves
        {
            get;
            set;
        }
        /// <summary>
        /// 
        /// </summary>
        public PointView[] Points
        {
            get;
            set;
        }
        /// <summary>
        /// 
        /// </summary>
        public MoveOptionList MoveOptions
        {
            get;
            set;
        }
        /// <summary>
        /// 
        /// </summary>
        public MoveHistoryList MoveHistory
        {
            get;
            set;
        }
        /// <summary>
        /// Current Player Turn
        /// </summary>
        public TurnView CurrentTurn
        {
            get
            {
                if (_currentTurn == null)
                    _currentTurn = new TurnView(Players[0]);

                return _currentTurn;
            }
        }
        private TurnView _currentTurn;

        #endregion
        #region Methods
        public void NextPlayerTurn()
        {

            if (CurrentTurn.Player.PlayerData.PlayerID == 1)
                _currentTurn = new TurnView(Players[1]);
            else
                _currentTurn = new TurnView(Players[0]);

            CurrentTurn.Player.PlayerData.Turns += 1;

            if (OnPlayerChanged != null)
                OnPlayerChanged.Invoke(_currentTurn.Player, null);

        }

        #endregion
        public MoveView CurrentTouch
        {
            get
            {
                return _currentTouch;
            }
            set
            {
                NotifyPropertyChanged("CurrentTouch");
                _currentTouch = value;
            }
        }
        private MoveView _currentTouch;
        #region Constructors
       
        public GameView(Canvas parent, GameModel.ThemeType theme, Boolean isDark, GameModel.DifficultyType difficulty, Byte x, Byte y, params PlayerView[] players)
        {
            Theme = theme;
            Parent = parent;
            GameData = new GameModel(x, y, isDark);
            MoveOptions = new MoveOptionList(x, y);
            MoveHistory = new MoveHistoryList(MoveOptions.Length);

            Difficulty = difficulty;
            Players = players;
            Moves = new MoveView[MoveHistory.Length];
            Points = new PointView[x * y + 1];

            var boxWidth = 30d;
            var boxDotWidth = 10d;
            var marginWidth = 5d;
            var totalWidth = (boxWidth + (marginWidth * 2) + boxDotWidth);

            var pointIndex = 0;
            //FIX - This should be a single user or server control
            for (Byte iY = 0; iY <= GameData.Rows; iY++)
            {
                for (Byte iX = 0; iX <= GameData.Columns; iX++)
                {

                    if (Theme == GameModel.ThemeType.Arcade)
                    {
                        var dotElement = new Rectangle();
                        parent.Children.Add(dotElement);
                        dotElement.Opacity = 0.50d;

                        dotElement.Height = boxDotWidth;
                        dotElement.Width = boxDotWidth;
                        dotElement.Fill = GetForegroundColor();

                        Canvas.SetLeft(dotElement, (iX * totalWidth));
                        Canvas.SetTop(dotElement, (iY * totalWidth));
                    }
                    else
                    {
                        var dotElement = new Ellipse();
                        parent.Children.Add(dotElement);
                        dotElement.Opacity = 0.50d;

                        dotElement.Height = boxDotWidth / 1.5;
                        dotElement.Width = boxDotWidth / 1.5;
                        dotElement.Fill = GetForegroundColor();

                        Canvas.SetLeft(dotElement, (iX * totalWidth) + (boxDotWidth/4));
                        Canvas.SetTop(dotElement, (iY * totalWidth) + (boxDotWidth/4));
                    }


                    if (iX < GameData.Columns)
                    {
                        var xElementUI = new Rectangle();
                        parent.Children.Add(xElementUI);
                        xElementUI.Height = (Theme == GameModel.ThemeType.Arcade)? boxDotWidth : 3;
                        xElementUI.Width = boxWidth;
                        xElementUI.Fill = GetForegroundColor();
                        xElementUI.Opacity = (Theme == GameModel.ThemeType.Arcade) ? 0.18d : 0.04d;

                        var xElementUIX = (iX * totalWidth) + marginWidth + boxDotWidth;
                        var xElementUIY = iY * totalWidth;

                        if  (Theme == GameModel.ThemeType.Traditional)
                            xElementUIY += ((boxDotWidth/2d) - 1.5d);

                        Canvas.SetLeft(xElementUI, xElementUIX);
                        Canvas.SetTop(xElementUI, xElementUIY);

                        var xElementTouch = new Rectangle();
                        parent.Children.Add(xElementTouch);
                        xElementTouch.Height = boxWidth + marginWidth;
                        xElementTouch.Width = boxWidth + marginWidth;
                        xElementTouch.Fill = new SolidColorBrush(Colors.Transparent);
                        xElementTouch.RenderTransform = new RotateTransform() { Angle = 45 };
                        Canvas.SetZIndex(xElementTouch, 1000);
                        var location = new Point((iX * totalWidth + marginWidth + boxDotWidth) + (totalWidth / 3d), (iY * totalWidth + marginWidth + boxDotWidth) - (totalWidth / 1.5d));
                        Canvas.SetLeft(xElementTouch, location.X);
                        Canvas.SetTop(xElementTouch, location.Y);

                        var xElementTouchUI = new Polygon();
                        parent.Children.Add(xElementTouchUI);

                        xElementTouchUI.Points = new PointCollection(){
                            new Point(0,0),
                            new Point(0, boxWidth * 2),
                            new Point(boxWidth * 3, boxWidth),
                            new Point(boxWidth * 6,  0),
                            new Point(boxWidth * 6, boxWidth * 2),
                            new Point(boxWidth * 3, boxWidth)
                        };
                        xElementTouchUI.Opacity = 0.00d;
                        Canvas.SetZIndex(xElementTouchUI, 50);
                        Canvas.SetLeft(xElementTouchUI, location.X - (boxWidth * 3d));
                        Canvas.SetTop(xElementTouchUI, location.Y - (boxWidth / 4.5d));
                        var moveID = (Byte)((iX + 1) + (iY * ((GameData.Columns * 2) + 1)));
                        var moveView = new MoveHorizontalView(this, moveID, location, xElementUI, xElementTouch, xElementTouchUI);
                        Moves[moveView.MoveID - 1] = moveView;

                    }

                    if (iY < GameData.Rows)
                    {
                        var yElementUI = new Rectangle();
                        parent.Children.Add(yElementUI);
                        yElementUI.Height = boxWidth;
                        yElementUI.Width = (Theme == GameModel.ThemeType.Arcade) ? boxDotWidth : 3;
                        yElementUI.Fill = GetForegroundColor();
                        yElementUI.Opacity = (Theme == GameModel.ThemeType.Arcade)? 0.18d : 0.04d;

                        var yElementUIX = iX * totalWidth;
                        var yElementUIY = (iY * totalWidth) + marginWidth + boxDotWidth;

                        if (Theme == GameModel.ThemeType.Traditional)
                            yElementUIX += ((boxDotWidth / 2d) - 1.5d);

                        Canvas.SetLeft(yElementUI, yElementUIX);
                        Canvas.SetTop(yElementUI, yElementUIY);

                        var yElementTouch = new Rectangle();
                        parent.Children.Add(yElementTouch);
                        yElementTouch.Height = boxWidth;
                        yElementTouch.Width = boxWidth;
                        yElementTouch.Fill = new SolidColorBrush(Colors.Transparent);
                        yElementTouch.RenderTransform = new RotateTransform() { Angle = 45 };
                        Canvas.SetZIndex(yElementTouch, 1000);
                        var location = new Point((iX * totalWidth + marginWidth + boxDotWidth) - (totalWidth / 6d), (iY * totalWidth + marginWidth + boxDotWidth) - (totalWidth / 10d));
                        Canvas.SetLeft(yElementTouch, location.X);
                        Canvas.SetTop(yElementTouch, location.Y);

                        var yElementTouchUI = new Polygon();
                        parent.Children.Add(yElementTouchUI);
                        yElementTouchUI.Points = new PointCollection(){
                            new Point(0,0),
                            new Point(boxWidth * 2, 0),
                            new Point(boxWidth, boxWidth * 3),
                            new Point(boxWidth * 2,  boxWidth * 6),
                            new Point(0, boxWidth * 6),
                            new Point(boxWidth, boxWidth * 3)
                        };
                        yElementTouchUI.Opacity = 0.0d;
                        Canvas.SetZIndex(yElementTouchUI, 50);
                        Canvas.SetLeft(yElementTouchUI, location.X - (boxWidth * 1.05d));
                        Canvas.SetTop(yElementTouchUI, location.Y - (boxWidth * 2.25d));

                        var moveID = (Byte)((iX + GameData.Columns + 1) + (iY * ((GameData.Columns * 2) + 1)));
                        var moveView = new MoveVerticalView(this, moveID, location, yElementUI, yElementTouch, yElementTouchUI);
                        Moves[moveView.MoveID - 1] = moveView;
                    }

                    if (iX < GameData.Columns && iY < GameData.Rows)
                    {
                        var boxElement = new Rectangle();
                        parent.Children.Add(boxElement);
                        boxElement.Height = boxWidth;
                        boxElement.Width = boxWidth;
                        boxElement.Fill = GetForegroundColor();

                        boxElement.Opacity = (Theme == GameModel.ThemeType.Arcade)?0.08d:0;

                        var boxElementX = iX * totalWidth + marginWidth + boxDotWidth;
                        var boxElementY = iY * totalWidth + marginWidth + boxDotWidth;

                        Canvas.SetLeft(boxElement, boxElementX);
                        Canvas.SetTop(boxElement, boxElementY);

                        var textElement = new TextBlock();
                        parent.Children.Add(textElement);
                        textElement.Foreground = Players[0].Color;
                        textElement.FontSize = boxWidth*0.70;
                        Canvas.SetLeft(textElement, boxElementX);
                        Canvas.SetTop(textElement, boxElementY);

                        var point = new PointView(this, (Byte)(pointIndex += 1), boxElement, textElement);
                        Points[PointModel.GetPointID(GameData.Columns, (Byte)(iX + 1), (Byte)(iY + 1)) - 1] = point;
                    }
                }
            }
            for (Byte i = 0; i < Moves.Length; i++)
            {
                var firstPoint = MoveOptions[i].FirstPoint;
                var secondPoint = MoveOptions[i].SecondPoint;

                if (firstPoint > 0)
                    Moves[i].OnPointAssigned += Points[firstPoint - 1].MoveAssigned;
                if (secondPoint > 0)
                    Moves[i].OnPointAssigned += Points[secondPoint - 1].MoveAssigned;

                Moves[i].OnActivationComplete += MoveActivationComplete;
                Moves[i].OnMoveMade += MoveMade;
            }
        }
        public void MoveMadeComplete(Byte[] pointsScored)
        {
            if (pointsScored.Length > 0)
            {
                if (MoveHistory.GetOpenMoveCount() == 0)
                {
                    var gameOverTimer = new DispatcherTimer();
                    gameOverTimer.Interval = new TimeSpan(MoveSpeed.Milliseconds * 4);
                    gameOverTimer.Tick += (obj, arg) =>
                    {
                        ((DispatcherTimer)obj).Stop();
                        OnGameOver.Invoke(null, new GameOverEventArgs(Players[(MoveHistory.GetScore(2) > MoveHistory.GetScore(1))?1:0]));
                    };
                    gameOverTimer.Start();
                    return;
                }
                else
                {
                    NextMove();
                }
            }
            else
            {
                NextPlayerTurn();
                if (CurrentTurn.Player.PlayerData.TypeOfPlayer == PlayerModel.PlayerType.Computer)
                    StartComputerMove();
            }
        }
        public void NextMove()
        {
            IsAutoMove = false;
            if (CurrentTurn.Player.PlayerData.TypeOfPlayer == PlayerModel.PlayerType.Computer)
            {
                StartComputerMove();
            }
            else
            {
                if (Difficulty == GameModel.DifficultyType.Easy && CurrentTurn.Moves.Count > 0)
                {
                    var offensiveMoves = MoveOptions.GetOpenOffensiveMoves(GameData);
                    if (offensiveMoves.Length > 0)
                    {
                        IsAutoMove = true;
                        AutoFillTimer = new DispatcherTimer();
                        AutoFillTimer.Interval = MoveSpeed;
                        AutoFillTimer.Tick += (obj, arg) =>
                        {
                            ((DispatcherTimer) obj).Stop();
                            Moves[offensiveMoves.GetRandom().MoveID - 1].Assign();
                        };
                        AutoFillTimer.Start();
                    }
                }
            }
        }
        public void MoveActivationComplete(object sender, EventArgs e)
        {
            if (OnAfterActivationComplete != null)
                OnAfterActivationComplete.Invoke(this, e);
        }
        public void MoveMade(object sender, MoveMadeEventArgs e)
        {
            if (OnAfterMoveMade != null)
                OnAfterMoveMade.Invoke(this, e);

            if (!e.IsValidMove)
                return;

            if (!IsPaused)
                MoveMadeComplete(e.PointsScored);
        }
        public void StartComputerMove()
        {
            ComputerTurnTimer = new DispatcherTimer();
            ComputerTurnTimer.Interval = MoveSpeed;
            ComputerTurnTimer.Tick += (obj, arg) =>
            {
                ((DispatcherTimer)obj).Stop();
                MakeComputerMove(obj, new PlayerEventArgs(CurrentTurn.Player));
            };
            ComputerTurnTimer.Start();
        }
        public void MakeComputerMove(object sender, PlayerEventArgs e)
        {
            if (OnComputerMoveStarted != null)
                OnComputerMoveStarted.Invoke(this, e);

            var threadDelegate = new ParameterizedThreadStart(MakeComputerMove);
            Thread newThread = new Thread(threadDelegate);
            newThread.Start(e.Player.PlayerData.PlayerID);

            //var moveID = GameModel.GetComputerMove(MoveOptions, MoveHistory, new SByte[] { 0, 1 }, e.Player.PlayerData.PlayerID, Difficulty, GameData).MoveID;
            //Moves[moveID - 1].Assign();
        }
        public void MakeComputerMove(Object playerID)
        {
            var moveID = GameModel.GetComputerMove(MoveOptions, MoveHistory, new SByte[] { 0, 1 }, (SByte)playerID, Difficulty, GameData).MoveID;

            Deployment.Current.Dispatcher.BeginInvoke(() =>
            {
                if (OnComputerMoveCompleted != null)
                    OnComputerMoveCompleted.Invoke(this, new PlayerEventArgs(Players[(SByte)playerID - 1]));
                    Moves[moveID - 1].Assign();
            });
        }
        #endregion Factory Methods
        #region INotifyPropertyChanged
        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }
        #endregion INotifyPropertyChanged

        internal void Start()
        {
            if (CurrentTurn.Player.PlayerData.TypeOfPlayer == PlayerModel.PlayerType.Computer)
                StartComputerMove();
        }
        public SolidColorBrush GetForegroundColor()
        {
            return new SolidColorBrush((Theme == GameModel.ThemeType.Arcade && GameData.IsDark) ? Colors.White : Colors.Black);
        }
        public static GameView Load(String gameData, Canvas parent, Boolean isDark)
        {
            if (String.IsNullOrEmpty(gameData))
                return null;

            var gameDatas = gameData.Split('\n');
            var theme = (GameModel.ThemeType)Enum.Parse(typeof(GameModel.ThemeType), gameDatas[4], true);
            var sizes = gameDatas[0].Split(',');

            var playersRaw = gameDatas[1].Split(',');
            var players = new PlayerView[playersRaw.Length];
            for (var i = 0; i < playersRaw.Length; i++)
            {
                var playerRaw = playersRaw[i].Split('|');
                var playerColorRaw = playerRaw[2].Split('~');
                var playerColor = Color.FromArgb(Byte.Parse(playerColorRaw[0]), Byte.Parse(playerColorRaw[1]),
                                                Byte.Parse(playerColorRaw[2]), Byte.Parse(playerColorRaw[3]));
                players[i] = new PlayerView(
                    SByte.Parse(playerRaw[0]),
                    playerRaw[1],
                    (theme == GameModel.ThemeType.Traditional)?playerColor:(i == 0)?PersistentSettings.Instance.AccentColor:PersistentSettings.Instance.SecondaryAccentColor,
                    (PlayerModel.PlayerType)Enum.Parse(typeof(PlayerModel.PlayerType), playerRaw[3], true)
                );
            }


            var difficulty = (GameModel.DifficultyType) Enum.Parse(typeof (GameModel.DifficultyType), gameDatas[3], true);

            var result = new GameView(parent,
                theme,
                isDark,
                difficulty,
                Byte.Parse(sizes[0]),
                Byte.Parse(sizes[1]),
                players);

            var movesRaw = gameDatas[2].Split(',');
            for (var i = (Byte)0; i < movesRaw.Length; i++)
            {
                var moveID = Byte.Parse(movesRaw[i]);
                if (moveID > 0)
                {
                    if (result.Moves[moveID - 1].Assign(true).Points.Length == 0)
                        result.NextPlayerTurn();
                }
            }
            return result;
        }

        public override String ToString()
        {
            var playersRaw = String.Empty;
            foreach (var player in Players)
            {
                var colorRaw = String.Format("{0}~{1}~{2}~{3}", player.Color.Color.A, player.Color.Color.R, player.Color.Color.G, player.Color.Color.B);
                playersRaw += ((playersRaw == String.Empty) ? "" : ",") + String.Format("{0}|{1}|{2}|{3}", player.PlayerData.PlayerID, player.PlayerData.Name, colorRaw, player.PlayerData.TypeOfPlayer);
            }
            return String.Format("{0}\n{1}\n{2}\n{3}\n{4}", GameData, playersRaw, MoveHistory, Difficulty, Theme);
        }
    }
}